// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {mergeCallsites} from './flamegraph_util';
import {CallsiteInfo} from './state';

test('zeroCallsitesMerged', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 4,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 2,
      name: 'B4',
      depth: 1,
      totalSize: 4,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 5);

  // Small callsites are not next ot each other, nothing should be changed.
  expect(mergedCallsites).toEqual(callsites);
});

test('zeroCallsitesMerged2', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 6,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 4,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 5,
      parentId: 2,
      name: 'B5',
      depth: 1,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 5);

  // Small callsites are not next ot each other, nothing should be changed.
  expect(mergedCallsites).toEqual(callsites);
});

test('twoCallsitesMerged', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 6);

  expect(mergedCallsites).toEqual([
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: '[merged]',
      depth: 1,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
  ]);
});

test('manyCallsitesMerged', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 3,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 5,
      parentId: 1,
      name: 'A5',
      depth: 1,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 6,
      parentId: 3,
      name: 'A36',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 7,
      parentId: 4,
      name: 'A47',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 8,
      parentId: 5,
      name: 'A58',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const expectedMergedCallsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: '[merged]',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 6,
      parentId: 3,
      name: '[merged]',
      depth: 2,
      totalSize: 3,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 4);

  // In this case, callsites A3, A4 and A5 should be merged since they are
  // smaller then 4 and are on same depth with same parent. Callsites A36, A47
  // and A58 should also be merged since their parents are merged.
  expect(mergedCallsites).toEqual(expectedMergedCallsites);
});

test('manyCallsitesMergedWithoutChildren', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 3,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 5,
      parentId: 1,
      name: 'A5',
      depth: 1,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 6,
      parentId: 2,
      name: 'B6',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 7,
      parentId: 4,
      name: 'A47',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 8,
      parentId: 6,
      name: 'B68',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const expectedMergedCallsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: '[merged]',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 6,
      parentId: 2,
      name: 'B6',
      depth: 1,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 7,
      parentId: 3,
      name: 'A47',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 8,
      parentId: 6,
      name: 'B68',
      depth: 2,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 4);

  // In this case, callsites A3, A4 and A5 should be merged since they are
  // smaller then 4 and are on same depth with same parent. Callsite A47
  // should not be merged with B68 althought they are small because they don't
  // have sam parent. A47 should now have parent A3 because A4 is merged.
  expect(mergedCallsites).toEqual(expectedMergedCallsites);
});

test('smallCallsitesNotNextToEachOtherInArray', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 20,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 1,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 5,
      parentId: 1,
      name: 'A5',
      depth: 1,
      totalSize: 3,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const expectedMergedCallsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 20,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: '[merged]',
      depth: 1,
      totalSize: 4,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 8,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 4);

  // In this case, callsites A3, A4 and A5 should be merged since they are
  // smaller then 4 and are on same depth with same parent. Callsite A47
  // should not be merged with B68 althought they are small because they don't
  // have sam parent. A47 should now have parent A3 because A4 is merged.
  expect(mergedCallsites).toEqual(expectedMergedCallsites);
});

test('smallCallsitesNotMerged', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: 1,
      name: 'A2',
      depth: 1,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 1);

  expect(mergedCallsites).toEqual(callsites);
});

test('mergingRootCallsites', () => {
  const callsites: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const mergedCallsites = mergeCallsites(callsites, 20);

  expect(mergedCallsites).toEqual([
    {
      id: 1,
      parentId: -1,
      name: '[merged]',
      depth: 0,
      totalSize: 12,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
  ]);
});

test('largerFlamegraph', () => {
  const data: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 60,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 40,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 25,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: 'A4',
      depth: 1,
      totalSize: 15,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 5,
      parentId: 1,
      name: 'A5',
      depth: 1,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 6,
      parentId: 1,
      name: 'A6',
      depth: 1,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 7,
      parentId: 2,
      name: 'B7',
      depth: 1,
      totalSize: 30,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 8,
      parentId: 2,
      name: 'B8',
      depth: 1,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 9,
      parentId: 3,
      name: 'A39',
      depth: 2,
      totalSize: 20,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 10,
      parentId: 4,
      name: 'A410',
      depth: 2,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 11,
      parentId: 4,
      name: 'A411',
      depth: 2,
      totalSize: 3,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 12,
      parentId: 4,
      name: 'A412',
      depth: 2,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 13,
      parentId: 5,
      name: 'A513',
      depth: 2,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 14,
      parentId: 5,
      name: 'A514',
      depth: 2,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 15,
      parentId: 7,
      name: 'A715',
      depth: 2,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 16,
      parentId: 7,
      name: 'A716',
      depth: 2,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 17,
      parentId: 7,
      name: 'A717',
      depth: 2,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 18,
      parentId: 7,
      name: 'A718',
      depth: 2,
      totalSize: 5,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 19,
      parentId: 9,
      name: 'A919',
      depth: 3,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 20,
      parentId: 17,
      name: 'A1720',
      depth: 3,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  const expectedData: CallsiteInfo[] = [
    {
      id: 1,
      parentId: -1,
      name: 'A',
      depth: 0,
      totalSize: 60,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 2,
      parentId: -1,
      name: 'B',
      depth: 0,
      totalSize: 40,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 3,
      parentId: 1,
      name: 'A3',
      depth: 1,
      totalSize: 25,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 4,
      parentId: 1,
      name: '[merged]',
      depth: 1,
      totalSize: 35,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 7,
      parentId: 2,
      name: 'B7',
      depth: 1,
      totalSize: 30,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 8,
      parentId: 2,
      name: 'B8',
      depth: 1,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 9,
      parentId: 3,
      name: 'A39',
      depth: 2,
      totalSize: 20,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 10,
      parentId: 4,
      name: '[merged]',
      depth: 2,
      totalSize: 25,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 15,
      parentId: 7,
      name: '[merged]',
      depth: 2,
      totalSize: 25,
      selfSize: 0,
      mapping: 'x',
      merged: true,
      highlighted: false
    },
    {
      id: 19,
      parentId: 9,
      name: 'A919',
      depth: 3,
      totalSize: 10,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
    {
      id: 20,
      parentId: 15,
      name: 'A1720',
      depth: 3,
      totalSize: 2,
      selfSize: 0,
      mapping: 'x',
      merged: false,
      highlighted: false
    },
  ];

  // In this case, on depth 1, callsites A4, A5 and A6 should be merged and
  // initiate merging of their children A410, A411, A412, A513, A514. On depth2,
  // callsites A715, A716, A717 and A718 should be merged.
  const actualData = mergeCallsites(data, 16);

  expect(actualData).toEqual(expectedData);
});
